package com.ecp.commons.db;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.stereotype.Repository;

import com.ecp.commons.bean.Page;
import com.ecp.commons.dao.IDSDAO;
import com.ecp.commons.serialization.SerializableObject;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;

@Repository("mongoDAO")
public class MongoDAOImpl<T extends SerializableObject> implements MongoDAO<T> {
	protected DB		db		= null;

	protected IDSDAO	idsDAO	= null;

	@Resource(name = "mongodb")
	public void setDB(MongoDB mongodb) {
		db = mongodb.getMongoDB();
	}

	@Resource(name = "idsDAO")
	public void setIdsDAO(IDSDAO idsDAO) {
		this.idsDAO = idsDAO;
	}

	@SuppressWarnings("unchecked")
	@Override
	public T load(long id, T t) {

		T instance = null;
		DBCollection coll = db.getCollection(t.getEntity());
		DBObject dbObj = coll.findOne(new BasicDBObject("_id", id));
		if (dbObj != null) {
			instance = (T) this.fromDBObject(dbObj, t);
		}
		return instance;
	}

	@Override
	public long add(T t) throws Exception {
		if (t.getLong("id") == 0) {
			t.set("id", idsDAO.generateId(t.getEntity()));
		}
		DBCollection coll = db.getCollection(t.getEntity());
		coll.insert(this.toDBObject(t));
		return t.getLong("id");
	}

	@Override
	public void delete(long id, T t) {
		DBCollection coll = db.getCollection(t.getEntity());
		coll.remove(new BasicDBObject("_id", id));
	}

	@Override
	public void update(T t) {
		this.save(t);
	}

	public void save(T t) {
		DBCollection coll = db.getCollection(t.getEntity());
		coll.save(this.toDBObject(t));
	}

	@SuppressWarnings("unchecked")
	@Override
	public T getEntity(Map<String, Object> condition, T t) {
		T instance = null;
		QueryBuilder query = new QueryBuilder();
		for (String key : condition.keySet()) {
			query.put(key).is(condition.get(key));
		}
		DBCollection coll = db.getCollection(t.getEntity());
		DBObject dbObj = coll.findOne(query.get());
		if (dbObj != null) {
			instance = (T) this.fromDBObject(dbObj, t);
		}
		return instance;
	}

	@SuppressWarnings("unchecked")
	public T getEntity(String[] keys, Object[] args, T t) {
		T instance = null;
		QueryBuilder query = new QueryBuilder();
		for (int i = 0; i < keys.length; i++) {
			query.put(keys[i]).is(args[i]);
		}
		DBCollection coll = db.getCollection(t.getEntity());
		DBObject dbObj = coll.findOne(query.get());
		if (dbObj != null) {
			instance = (T) this.fromDBObject(dbObj, t);
		}
		return instance;
	}

	@Override
	public List<T> getEntityList(Map<String, Object> condition, T t, Page page, String orderby) {
		List<T> list = new ArrayList<T>();
		QueryBuilder query = new QueryBuilder();
		for (String key : condition.keySet()) {
			query.put(key).is(condition.get(key));
		}
		DBCollection coll = db.getCollection(t.getEntity());
		DBCursor cursor = coll.find(query.get());
		if (orderby != null && orderby.length() > 0) {
			BasicDBObject sort = new BasicDBObject();
			for (String item : orderby.split(",")) {
				item = item.trim();
				boolean desc = item.toLowerCase().endsWith(" desc");
				if (desc) {
					sort.put(item.substring(0, item.length() - 5), -1);
				} else {
					sort.put(item, 1);
				}
			}
			cursor.sort(sort);
		}
		if (page != null) {
			page.setTotalCount((int) coll.count(query.get()));
			cursor.skip(page.getStart()).limit(page.getPageSize());
		}
		while (cursor.hasNext()) {
			DBObject dbObj = cursor.next();
			@SuppressWarnings("unchecked")
			T instance = (T) this.fromDBObject(dbObj, t);
			list.add(instance);
		}

		return list;
	}

	@Override
	public JSONObject toJSON(T t, String[] keys) {
		return this.toJSON(t, keys, "");
	}

	@Override
	public JSONObject toJSON(T t, String[] keys, String prefix) {
		if (prefix == null) {
			prefix = "";
		}
		JSONObject jobj = new JSONObject();
		SerializableObject obj = (SerializableObject) t;
		try {
			if (keys == null) {
				for (String key : obj.keySet()) {
					jobj.put(prefix + key, obj.getString(key));
				}
			} else {
				for (String key : keys) {
					if (obj.keySet().contains(key)) {
						jobj.put(prefix + key, obj.getString(key));
					}
				}
			}
		} catch (JSONException e) {
			e.printStackTrace();
		}
		return jobj;
	}

	@Override
	public JSONArray toJSONArray(List<T> list, String keys[]) {
		JSONArray jarr = new JSONArray();
		for (T t : list) {
			jarr.put(this.toJSON(t, keys, ""));
		}
		return jarr;
	}

	protected SerializableObject fromDBObject(DBObject obj, SerializableObject template) {
		if (obj == null) {
			return null;
		}
		SerializableObject instance = null;
		try {
			instance = template.getClass().newInstance();
			instance.set("id", obj.get("_id").toString());
			for (String key : template.keySet()) {
				if (!template.isPersistent(key)) {
					continue;
				}
				String columnName = template.getAliasName(key);
				if (columnName == null) {
					columnName = key;
				}
				Object value = obj.get(columnName);
				if (value != null) {
					if (value instanceof Boolean) {
						instance.set(key, (Boolean) value);
					} else if (value instanceof Date) {
						instance.set(key, (Date) value);
					} else if (value instanceof Double) {
						instance.set(key, (Double) value);
					} else if (value instanceof Integer) {
						instance.set(key, (Integer) value);
					} else if (value instanceof Long) {
						instance.set(key, (Long) value);
					} else if (value instanceof String) {
						instance.set(key, (String) value);
					} else {
						instance.set(key, value.toString());
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return instance;
	}

	protected BasicDBObject toDBObject(SerializableObject obj) {
		BasicDBObject dbObj = new BasicDBObject();
		for (String key : obj.keySet()) {
			String columnName = obj.getAliasName(key);
			if (columnName == null) {
				columnName = key;
			}
			if (key.equals("id")) {
				columnName = "_id";
			}
			switch (obj.getVariantType(key)) {
			case BOOLEAN:
				dbObj.put(columnName, obj.getBoolean(key));
				break;
			case DATETIME:
				dbObj.put(columnName, obj.getDate(key));
				break;
			case DOUBLE:
				dbObj.put(columnName, obj.getDouble(key));
				break;
			case INTEGER:
				dbObj.put(columnName, obj.getInt(key));
				break;
			case STRING:
				dbObj.put(columnName, obj.getString(key));
				break;
			default:
				dbObj.put(columnName, obj.getString(key));
				break;
			}
		}
		return dbObj;
	}
}
